package tump;

import tools.BitArrayUnits;
import tools.DateStrTanslationUnits;

import java.nio.ByteBuffer;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;

/**
 * @Author: sun
 * @Description:模仿GeoHashUtil，由time和经纬度共同构成的TimeGeoHash，长度为50bit，转base32为10个字符
 * @Data: create in 17:46 2019/1/26
 * @Modfied by:
 */
public class TimeGeoHashUtil {
    private TimeLocationBean timeLocationBean;
    /**
     * 1 2500km;2 630km;3 78km;4 30km
     * 5 2.4km; 6 610m; 7 76m; 8 19m
     */
    /**经纬度转化为geohash长度:20bit纬度，20bit精度，20bit时间划分*/
    private int hashLength = 12;
    /**纬度转化为二进制长度*/
    private int latLength = 20;
    /**经度转化为二进制长度*/
    private int lngLength = 20;
    /**时间转换为二进制的长度*/
    private int timeLength = 20;
    // TODO: 2019/1/26 设计系统的初始时间到底是怎么算，即时间如何分片
    /**初始时间和截止时间，现在系统的初试时间为月的第一天0秒，这里还没有想好，暂定*/
    private double startTime;
    private double endTime;
    /**每格纬度的单位大小*/
    private double minLat;
    /**每个经度的倒下*/
    private double minLng;
//    private static final char[] CHARS = {'0', '1', '2', '3', '4', '5', '6', '7',
//            '8', '9', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'm', 'n',
//            'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};

    private static final char[] CHARS = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
            'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
            'V', 'W', 'X', 'Y', 'Z', '2', '3', '4', '5', '6', '7'};

    public TimeGeoHashUtil(double lat, double lng,double time,double startTime,double endTime) {
        timeLocationBean = new TimeLocationBean(lat, lng,time);
        this.startTime = startTime;
        this.endTime = endTime;
        setMinLatLng();
    }

    /**
     * @Author:sun
     * @Description: 获取生成的Base32字符串长度
     */
    public int gethashLength() {
        return hashLength;
    }

     /**
     * @Author:sun
     * @Description: 设置经纬度的最小单位
     */
    private void setMinLatLng() {
        minLat = LocationBean.MAXLAT - LocationBean.MINLAT;
        for (int i = 0; i < latLength; i++) {
            minLat /= 2.0;
        }
        minLng = LocationBean.MAXLNG - LocationBean.MINLNG;
        for (int i = 0; i < lngLength; i++) {
            minLng /= 2.0;
        }
    }

//    /**
//     * @return
//     * @Author:sun
//     * @Description: 求所在坐标点及周围点组成的九个点的Base32值
//     */
//    public List<String> getGeoHashBase32For9() {
//        double leftLat = timeLocationBean.getLat() - minLat;
//        double rightLat = timeLocationBean.getLat() + minLat;
//        double upLng = timeLocationBean.getLng() - minLng;
//        double downLng = timeLocationBean.getLng() + minLng;
//        List<String> base32For9 = new ArrayList<String>();
//        //左侧从上到下 3个
//        String leftUp = getGeoHashBase32(leftLat, upLng);
//        if (!(leftUp == null || "".equals(leftUp))) {
//            base32For9.add(leftUp);
//        }
//        String leftMid = getGeoHashBase32(leftLat, timeLocationBean.getLng());
//        if (!(leftMid == null || "".equals(leftMid))) {
//            base32For9.add(leftMid);
//        }
//        String leftDown = getGeoHashBase32(leftLat, downLng);
//        if (!(leftDown == null || "".equals(leftDown))) {
//            base32For9.add(leftDown);
//        }
//        //中间从上到下 3个
//        String midUp = getGeoHashBase32(timeLocationBean.getLat(), upLng);
//        if (!(midUp == null || "".equals(midUp))) {
//            base32For9.add(midUp);
//        }
//        String midMid = getGeoHashBase32(timeLocationBean.getLat(), timeLocationBean.getLng());
//        if (!(midMid == null || "".equals(midMid))) {
//            base32For9.add(midMid);
//        }
//        String midDown = getGeoHashBase32(timeLocationBean.getLat(), downLng);
//        if (!(midDown == null || "".equals(midDown))) {
//            base32For9.add(midDown);
//        }
//        //右侧从上到下 3个
//        String rightUp = getGeoHashBase32(rightLat, upLng);
//        if (!(rightUp == null || "".equals(rightUp))) {
//            base32For9.add(rightUp);
//        }
//        String rightMid = getGeoHashBase32(rightLat, timeLocationBean.getLng());
//        if (!(rightMid == null || "".equals(rightMid))) {
//            base32For9.add(rightMid);
//        }
//        String rightDown = getGeoHashBase32(rightLat, downLng);
//        if (!(rightDown == null || "".equals(rightDown))) {
//            base32For9.add(rightDown);
//        }
//        return base32For9;
//   }


    /**
     * @return
     * @Author:sun
     * @Description: 获取经纬度的base32字符串
     */
    public String getGeoHashBase32() {
        return getGeoHashBase32(timeLocationBean.getLat(), timeLocationBean.getLng(),timeLocationBean.getTime());
    }


    /**
     * @param lat
     * @param lng
     * @param time
     * @return
     * @Author:lulei
     * @Description: 获取经纬度的base32字符串
     */
    private String getGeoHashBase32(double lat, double lng,double time) {
        boolean[] bools = getGeoBinary(lat, lng,time);
        if (bools == null) {
            return null;
        }
       StringBuffer sb = new StringBuffer();
        for (int i = 0; i < bools.length; i = i + 5) {
            boolean[] base32 = new boolean[5];
            for (int j = 0; j < 5; j++) {
                base32[j] = bools[i + j];
            }
            char cha = getBase32Char(base32);
            if (' ' == cha) {
                return null;
            }
            sb.append(cha);
        }
        return sb.toString();
    }

    /**
     * @param lat
     * @param lng
     * @param time
     * @return
     * @Author:sun
     * @Description: 获取坐标的geo二进制字符串
     */
    private boolean[] getGeoBinary(double lat, double lng,double time) {
        boolean[] latArray = getHashArray(lat, LocationBean.MINLAT, LocationBean.MAXLAT, latLength);
        boolean[] lngArray = getHashArray(lng, LocationBean.MINLNG, LocationBean.MAXLNG, lngLength);
        boolean[] timeArray = getTimeHashArray(time,timeLength);
        return merge(latArray, lngArray,timeArray);
    }


    /**
     * @param value
     * @param min
     * @param max
     * @return
     * @Author:sun
     * @Description: 将数字转化为geohash二进制字符串
     */
    private boolean[] getHashArray(double value, double min, double max, int length) {
        if (value < min || value > max) {
            return null;
        }
        if (length < 1) {
            return null;
        }
        boolean[] result = new boolean[length];
        for (int i = 0; i < length; i++) {
            double mid = (min + max) / 2.0;
            if (value > mid) {
                result[i] = true;
                min = mid;
            } else {
                result[i] = false;
                max = mid;
            }
        }
        return result;
    }

    /**
     * @param time
     * @author sun
     * @Description 将时间转为二进制串
     */
    private boolean[] getTimeHashArray(double time,int length){
        if (time < this.startTime || time > this.endTime) {
            return null;
        }
        if (length < 1) {
            return null;
        }
        double unit = this.endTime-this.startTime;
        double postTime = endTime;
        double preTime = startTime;
        boolean[] result = new boolean[length];
        SimpleDateFormat df = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
        for (int i = 0; i < length; i++) {
            double mid = (preTime + postTime) / 2.0;
//            System.out.println("pre:"+df.format(new Date(new Double(preTime).longValue()))
//                    +"\t mid:"+df.format(new Date(new Double(mid).longValue()))
//                    +"\t post:"+df.format(new Date(new Double(postTime).longValue()))
//                    +"\t ");
            if (time > mid) {
                result[i] = true;
                preTime = mid;
            } else {
                result[i] = false;
                postTime = mid;
            }
        }
        return result;

    }

    /**
     * @param latArray
     * @param lngArray
     * @param timeArray
     * @return
     * @Author:lulei
     * @Description: 合并经纬度还有时间二进制
     */
    private boolean[] merge(boolean[] latArray, boolean[] lngArray,boolean[] timeArray) {
        if (latArray == null || lngArray == null|| timeArray==null) {
            return null;
        }
        boolean[] result = new boolean[lngArray.length + latArray.length+timeArray.length];
        Arrays.fill(result, false);
        for (int i = 0; i < lngArray.length; i++) {
            result[3 * i] = lngArray[i];
        }
        for (int i = 0; i < latArray.length; i++) {
            result[3 * i + 1] = latArray[i];
        }
        for (int i = 0; i < timeArray.length; i++) {
            result[3 * i + 2] = latArray[i];
        }
        return result;
    }

 /**
     * @param base32
     * @return
     * @Author:lulei
     * @Description: 将五位二进制转化为base32
     */
    private char getBase32Char(boolean[] base32) {
        if (base32 == null || base32.length != 5) {
            return ' ';
        }
        int num = 0;
        for (boolean bool : base32) {
            num <<= 1;
            if (bool) {
                num += 1;
            }
        }
        return CHARS[num % CHARS.length];
    }

    public static void main(String[] args) throws Exception {

        double time = DateStrTanslationUnits.dateToDouble("2016.12.31-23:00:00");
        double startTime =   DateStrTanslationUnits.dateToDouble("2016.12.01-00:00:00");
        double endTime =  DateStrTanslationUnits.dateToDouble("2016.12.31-24:00:00");
 ;
        TimeGeoHashUtil timeGeoHashUtil = new TimeGeoHashUtil(40.222012, 116.248283,time,startTime,endTime);
        System.out.println(timeGeoHashUtil.getGeoHashBase32());
        System.out.println("Base32转byte数组");
        byte[] bArr = Base32Util.decode(timeGeoHashUtil.getGeoHashBase32());
        System.out.println("Base32转byte数组的长度length:"+bArr.length);
        for (byte b:bArr) {
            System.out.println(BitArrayUnits.byteToBit(b));
        }

        Long myLong1 = 0b0000111100011111011000100011100000111011100011100000000111011100L;
        Long myLong2 = 0b00001111000111110110001000111000001110111000111000000001110111000000L;
        System.out.println("myLong1:"+myLong1);
        System.out.println("myLong2:"+(myLong2>>>4));
        ByteBuffer buffer = ByteBuffer.allocate(8);
        buffer.put(bArr, 0, bArr.length);
        buffer.flip();//need flip
        Long myLong3 = buffer.getLong()>>>4;
        System.out.println("muLong3:"+myLong3);

        buffer.putLong(0,  0b00001111000111110110001000111000001110111000111000000001110111000000L);
        byte[] b2 = buffer.array();
        for (byte b:b2) {
            System.out.println(BitArrayUnits.byteToBit(b));
        }

        System.out.println("测试：");
        Long value = myLong2>>>4;
        System.out.println(value>>>57);
        System.out.println(value>>>54);
        System.out.println(value>>>51);

        /**
         * 对于数据如何快速分区，只要将八叉树的值用long型存储，在前面补4个0，凑够64位，
         * 这样对不同层级的树，只要将long值无符号右移3*（20-level）位，得到的值就是对应祖先节点的值，
         * 这样对数据快速分区
         * 对于八叉树，每个节点的编号就是（父节点值）*8+i 其中i=1到7
         * */

    }
}

